<template>
  <div class="statistics-container">
    <div class="header-tools">
      <h3 style="margin: 0 0 0 1em; line-height: 50px; float: left">已上传（{{ pUserSnapshots.length }}人）</h3>
      <a-space>
        <a-input placeholder="输入姓名搜索" style="width: 150px" v-model:value="userNameFilter" />
        <a-button type="primary" :disabled="comparingUserOriginalShapes.length < 2" @click="onCompareUsers">对比
        </a-button>
        <a-button :type="collection.length ? 'primary' : 'default'" shape="circle" title="收藏夹"
          @click="onOpenCollection">
          <template #icon>
            <StarOutlined />
          </template>
        </a-button>
        <a-checkbox :checked="isSortByTime" @change="isSortByTime = !isSortByTime">按时间排序</a-checkbox>
        <a-checkbox :checked="isAllUserNamesVisible" @change="isAllUserNamesVisible = !isAllUserNamesVisible">显示姓名
        </a-checkbox>
      </a-space>
    </div>
    <!-- 统计图 -->
    <div>
      <div id="classifiyStatistics"></div>
    </div>
    <div class="user-list">
      <div v-for="(userWork, i) in pUserSnapshots" :key="i" class="user">
        <div class="thumbnails-container">
          <p class="tips" v-if="userWork.works.length === 0">未上传</p>
          <div v-else class="thumbnail" @click="previewUserWorks(userWork.userId)">
            <img :src="userWork.works[0].imageDataURL" />
          </div>
          <div class="count" v-if="userWork.works.length > 1">{{ userWork.works.length }}</div>
          <div class="thumbnail-tools" v-if="userWork.works.length > 0">
            <a-button :type="isInCollection(userWork, userWork.works[0])
          ? 'primary'
          : 'default'
        " shape="circle" @click="switchCollection(userWork, userWork.works[0])">
              <template #icon>
                <StarOutlined />
              </template>
            </a-button>
          </div>
        </div>
        <div class="user-info">
          <a-checkbox @change="onCheckBox(userWork)"></a-checkbox>
          <div class="user-name">
            <span v-if="!userWork.userName || !userWork.userName.length">未知姓名</span>
            <span
              v-else-if="userWork.userName.length === 1 || !!userFullNameVisibility[userWork.userId] || isAllUserNamesVisible"
              @click="toggleFullName(userWork.userId)" style="cursor: pointer;">{{ userWork.userName }}</span>
            <span v-else style="cursor: pointer;" @click="toggleFullName(userWork.userId)">
              {{ userWork.userName[0] }}
              <span>
                {{
        userWork.userName
          .split("")
          .slice(1)
          .map((v) => "*")
          .join("")
      }}
              </span>
            </span>
          </div>
          <!-- 2023-04-07 暂时移除掉时间仅保留占位 -->
          <!-- <div class="update-time" v-if="userWork.lastUpdatedAt" :title="userWork.lastUpdatedAt">
            {{ userWork.lastUpdatedAt }}
          </div>
          <div v-else style="opacity: 0; color: transparent; overflow: hidden">占位</div> -->
          <div style="opacity: 0; color: transparent; overflow: hidden">占位</div>
        </div>
      </div>
    </div>
    <h3 style="padding: 0 1em; margin: 0; line-height: 50px; border-top: 1px solid #ddd">
      未上传（{{ nUserSnapshots.length }}人）
    </h3>
    <div class="n-user-list">
      <span class="n-user-name" v-for="(u, i) in nUserSnapshots" :key="i">{{ u.userName }}</span>
    </div>
    <a-modal v-model:visible="isSingleUserModalVisible" width="calc(100% - 40px)" :footer="null"
      style="top: 20px; padding: 0;" :bodyStyle="{ padding: '20px' }">
      <template #title>
        <div style="text-align: center" v-if="singleUserSnapshots">
          {{ formatName(singleUserSnapshots.userName) }}
          <!-- <span style="font-weight: 400; font-size: 12px; color: gray;">
            上传于 {{ singleUserSnapshots.works[getUserWorkIndex(singleUserSnapshots)].created_at }}
          </span> -->
        </div>
      </template>
      <div v-if="singleUserSnapshots &&
        singleUserSnapshots.works &&
        singleUserSnapshots.works[getUserWorkIndex(singleUserSnapshots)]
        ">
        <div class="preview-image-container" style="textAlign: center; verticalAlign: middle;">
          <div class="preview-image-wrapper" :style="{ display: 'inline-block' }">
            <img :style="{
        maxWidth: '100%',
        maxHeight: singleUserSnapshots.works.length > 1 ? 'calc(100vh - 275px)' : 'calc(100vh - 135px)',
        opacity: isPlaying || isPaused ? '0' : '1'
      }" :src="singleUserSnapshots.works[getUserWorkIndex(singleUserSnapshots)].imageDataURL" />
            <canvas ref="playingCanvasRef" :style="{ display: isPlaying || isPaused ? 'block' : 'none' }"></canvas>
          </div>
          <div>
            <a-button type="primary" size="large" shape="circle" class="play" @click="playPauseWork">
              <template #icon>
                <pause-circle-filled v-if="isPlaying" />
                <play-circle-filled v-else />
              </template>
            </a-button>
            <div style="display: inline-block; width: 200px; vertical-align: middle; margin: 0 1em;">
              <a-slider v-model:value="playingProgress" @change="onChangePlayingProgress" :min="0"
                :max="singleUserSnapshots.works[getUserWorkIndex(singleUserSnapshots)].operations.length" :step="1" />
            </div>
            <a-dropdown placement="top">
              <a-button type="text">{{ playingSpeedTimesName }}</a-button>
              <template #overlay>
                <a-menu>
                  <a-menu-item>
                    <a href="javascript:;" @click="setPlayingSpeed(1)">1倍速</a>
                  </a-menu-item>
                  <a-menu-item>
                    <a href="javascript:;" @click="setPlayingSpeed(2)">2倍速</a>
                  </a-menu-item>
                  <a-menu-item>
                    <a href="javascript:;" @click="setPlayingSpeed(3)">3倍速</a>
                  </a-menu-item>
                  <a-menu-item>
                    <a href="javascript:;" @click="setPlayingSpeed(6)">6倍速</a>
                  </a-menu-item>
                  <a-menu-item>
                    <a href="javascript:;" @click="setPlayingSpeed(8)">8倍速</a>
                  </a-menu-item>
                </a-menu>
              </template>
            </a-dropdown>
            <a-button :type="isInCollection(singleUserSnapshots, singleUserSnapshots.works[getUserWorkIndex(singleUserSnapshots)])
          ? 'primary'
          : 'default'
        " shape="circle" class="favourite" size="large" :title="isInCollection(singleUserSnapshots, singleUserSnapshots.works[getUserWorkIndex(singleUserSnapshots)])
          ? '从收藏夹移除'
          : '添加到收藏夹'
        " @click="
        switchCollection(singleUserSnapshots, singleUserSnapshots.works[getUserWorkIndex(singleUserSnapshots)])
        ">
              <template #icon>
                <StarOutlined />
              </template>
            </a-button>
          </div>
        </div>

        <div v-if="singleUserSnapshots.works.length > 1" class="preview-thumbnails">
          <img v-for="(work, j) in singleUserSnapshots.works" :key="j" :class="{
        selected: j === userWorkIndexMap[singleUserId],
      }" :src="work.imageDataURL" :alt="j" @click="updateUserWorkIndex(singleUserId, j)" />
        </div>
      </div>
    </a-modal>
    <a-modal v-model:visible="isCollectionModalVisible" width="calc(100% - 40px)" :footer="null"
      style="top: 20px; padding: 0;" :bodyStyle="{ padding: '20px' }">
      <template #title>
        <div style="text-align: center" v-if="collection[collectionIndex]">
          {{ collection[collectionIndex].userName }}
          <!-- <span style="font-weight: 400; font-size: 12px; color: gray;">
            上传于 {{ collection[collectionIndex].created_at }}
          </span> -->
        </div>
        <div style="text-align: center" v-else>
          无截屏
        </div>
      </template>
      <div v-if="collection.length">
        <div style="text-align: center; position: relative;">
          <div style="position: relative; display: inline-block;">
            <img :style="{
        maxWidth: '100%',
        maxHeight: collection.length > 1 ? 'calc(100vh - 275px)' : 'calc(100vh - 135px)',
      }" :src="collection[collectionIndex].url" />
            <canvas ref="collectionPlayingCanvasRef" class="collection-playing-canvas"
              :style="{ display: isPlaying || isPaused ? 'block' : 'none' }"></canvas>
          </div>
          <div>
            <a-button type="primary" size="large" shape="circle" @click="playPauseWork">
              <template #icon>
                <pause-circle-filled v-if="isPlaying" />
                <play-circle-filled v-else />
              </template>
            </a-button>
            <div style="display: inline-block; width: 200px; vertical-align: middle; margin: 0 1em;">
              <a-slider v-model:value="playingProgress" @change="onChangePlayingProgress" :min="0"
                :max="collection[collectionIndex].operations.length" :step="1" />
            </div>
            <a-dropdown placement="top">
              <a-button type="text">{{ playingSpeedTimesName }}</a-button>
              <template #overlay>
                <a-menu>
                  <a-menu-item>
                    <a href="javascript:;" @click="setPlayingSpeed(1)">1倍速</a>
                  </a-menu-item>
                  <a-menu-item>
                    <a href="javascript:;" @click="setPlayingSpeed(2)">2倍速</a>
                  </a-menu-item>
                  <a-menu-item>
                    <a href="javascript:;" @click="setPlayingSpeed(3)">3倍速</a>
                  </a-menu-item>
                  <a-menu-item>
                    <a href="javascript:;" @click="setPlayingSpeed(6)">6倍速</a>
                  </a-menu-item>
                  <a-menu-item>
                    <a href="javascript:;" @click="setPlayingSpeed(8)">8倍速</a>
                  </a-menu-item>
                </a-menu>
              </template>
            </a-dropdown>
            <a-button
              :type="isInCollection(collection[collectionIndex], collection[collectionIndex]) ? 'primary' : 'default'"
              shape="circle" size="large" :title="isInCollection(collection[collectionIndex], collection[collectionIndex])
          ? '从收藏夹移除'
          : '添加到收藏夹'
        " @click="switchCollection(collection[collectionIndex], collection[collectionIndex])">
              <template #icon>
                <StarOutlined />
              </template>
            </a-button>
          </div>
        </div>

        <div v-if="collection.length > 1" class="preview-thumbnails">
          <img v-for="(work, j) in collection" :key="j" :class="{
        selected: j === collectionIndex,
      }" :src="work.url" :alt="j" @click="updateCollectionIndex(j)" />
        </div>
      </div>
      <p style="text-align: center; margin: 2em 0" v-else>尚未收藏学生截屏</p>
    </a-modal>
    <a-modal v-model:visible="isComparingModalVisible" :footer="null" width="100%" class="compare-modal">
      <template #title style="position: relative">
        <div class="compare-modal-title" style="position: relative">截屏对比
          <a-switch v-model:checked="isShowThumbnail" checkedChildren="显示缩略图" unCheckedChildren="隐藏缩略图"
            style="position:absolute;right:30px;top:0"></a-switch>
        </div>
      </template>
      <div class="comparing-list"
        style="display: flex;justify-content: space-between;flex-wrap:wrap;margin-top: -24px;max-height: calc(100% - 95px);overflow-y: auto">

        <div v-for="(userInfo, i) in comparingUserOriginalShapes" :key="i" class="compare-item"
          :style="i % 2 === 0 ? `width:calc(50% - 5px);border-right: 1px solid lightgray;border-bottom: 1px solid lightgray;padding-bottom: 40px` : `width:calc(50% - 5px);padding-bottom: 40px`">
          <div
            style="text-align: center;font-weight: 500;font-size: 16px;line-height: 1.5; margin: 1em 0;color:rgba(0,0,0,0.5)">
            {{ formatName(userInfo.userName) }}
          </div>
          <div v-if="userInfo &&
        userInfo.works &&
        userInfo.works[getCompareUserWorkIndex(userInfo)]
        ">
            <div class="preview-image-container" style="textAlign: center; verticalAlign: middle; ">
              <div class="preview-image-wrapper" :style="{ display: 'inline-block' }">
                <img :style="{
        maxWidth: '100%',
        maxHeight: userInfo.works.length > 1 ? 'calc(100vh - 275px)' : 'calc(100vh - 135px)',
        opacity: (!compareIsClick[userInfo.userId + '-' + getCompareWorkIndex(userInfo.userId)] && getProgressIndex(userInfo)) ? '1' : '0'
      }" :src="userInfo.works[comparingUserShapeIndexMap[userInfo.userId] || 0].imageDataURL" />
                <canvas ref="playingCompareCanvasRef"></canvas>
              </div>
              <div>
                <a-button type="primary" size="large" shape="circle" class="play"
                  @click="setCompareIsClick(userInfo.userId, i, compareIsClick[userInfo.userId + '-' + getCompareWorkIndex(userInfo.userId)])">
                  <template #icon>
                    <pause-circle-filled
                      v-if="compareIsClick[userInfo.userId + '-' + getCompareWorkIndex(userInfo.userId)]" />
                    <play-circle-filled v-else />
                  </template>
                </a-button>
                <div style="display: inline-block; width: 200px; vertical-align: middle; margin: 0 1em;">
                  <a-slider
                    v-model:value="compareUserProgressIndexMap[userInfo.userId + '-' + getCompareWorkIndex(userInfo.userId)]"
                    @change="onChangeComparePlayingProgress(compareUserProgressIndexMap[userInfo.userId + '-' + getCompareWorkIndex(userInfo.userId)] + '-' + userInfo.userId)"
                    :min="0" :max="userInfo.works[getCompareUserWorkIndex(userInfo)].operations.length - 1" :step="1" />
                </div>
                <a-dropdown placement="top">
                  <a-button type="text">
                    {{ getComparePlayingSpeed(userInfo.userId, getCompareUserWorkIndex(userInfo)) + '倍速' }}
                  </a-button>
                  <template #overlay>
                    <a-menu>
                      <a-menu-item>
                        <a href="javascript:;"
                          @click="setComparePlayingSpeed(1, userInfo.userId, getCompareUserWorkIndex(userInfo))">1倍速</a>
                      </a-menu-item>
                      <a-menu-item>
                        <a href="javascript:;"
                          @click="setComparePlayingSpeed(2, userInfo.userId, getCompareUserWorkIndex(userInfo))">2倍速</a>
                      </a-menu-item>
                      <a-menu-item>
                        <a href="javascript:;"
                          @click="setComparePlayingSpeed(3, userInfo.userId, getCompareUserWorkIndex(userInfo))">3倍速</a>
                      </a-menu-item>
                      <a-menu-item>
                        <a href="javascript:;"
                          @click="setComparePlayingSpeed(6, userInfo.userId, getCompareUserWorkIndex(userInfo))">6倍速</a>
                      </a-menu-item>
                      <a-menu-item>
                        <a href="javascript:;"
                          @click="setComparePlayingSpeed(8, userInfo.userId, getCompareUserWorkIndex(userInfo))">8倍速</a>
                      </a-menu-item>
                    </a-menu>
                  </template>
                </a-dropdown>
                <a-button :type="isInCollection(userInfo, userInfo.works[getCompareUserWorkIndex(userInfo)])
          ? 'primary'
          : 'default'
        " shape="circle" class="favourite" size="large" :title="isInCollection(userInfo, userInfo.works[getCompareUserWorkIndex(userInfo)])
          ? '从收藏夹移除'
          : '添加到收藏夹'
        " @click="
        switchCollection(userInfo, userInfo.works[getCompareUserWorkIndex(userInfo)])
        ">
                  <template #icon>
                    <StarOutlined />
                  </template>
                </a-button>
              </div>
            </div>
            <div v-if="isShowThumbnail">
              <div v-if="userInfo.works.length > 1 && !comparingUserShapeIndexMap[userInfo.userId]"
                class="preview-thumbnails">
                <img v-for="(work, j) in userInfo.works" :key="j" :class="{
        selected: j === 0,
      }" :src="work.imageDataURL" :alt="j" @click="setComparingUserShapeIndex(userInfo.userId, j)" />
              </div>
              <div v-if="userInfo.works.length > 1 && comparingUserShapeIndexMap[userInfo.userId]"
                class="preview-thumbnails">
                <img v-for="(work, j) in userInfo.works" :key="j" :class="{
        selected: j === comparingUserShapeIndexMap[userInfo.userId],
      }" :src="work.imageDataURL" :alt="j" @click="setComparingUserShapeIndex(userInfo.userId, j)" />
              </div>
            </div>
          </div>
        </div>
      </div>

    </a-modal>
  </div>
</template>

<script>
import * as echarts from 'echarts';
import { drawingRender } from './drawing.js';
// eslint-disable-next-line no-unused-vars

import orderBy from 'lodash.orderby';
import find from 'lodash.find';
import findIndex from 'lodash.findindex';
import debounce from 'lodash.debounce';
import { computed, onMounted, reactive, ref, toRefs, watch } from 'vue';
import utils from '@/common/utils';
import { PauseCircleFilled, PlayCircleFilled, StarOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';

function isArray(o) {
  return Object.prototype.toString.call(o) === '[object Array]';
}

export default {
  name: 'DataWrapper',
  components: { PlayCircleFilled, PauseCircleFilled, StarOutlined },
  props: {
    bookDir: { type: String, default: '' },
    widgetModel: { type: String, default: '' },
    results: { type: String, default: '[]' },
    students: { type: String, default: '' },
  },
  setup(props) {
    const { results, widgetModel, bookDir } = toRefs(props);
    const isSortByTime = ref(true);
    const isComparingModalVisible = ref(false);
    const comparingUserOriginalShapes = ref([]);
    const compareCanvasRefIndex = ref(0);
    const compareProgressArr = ref([]);

    const userWorkIndexMap = reactive({}); // { userId: index } 每个学生可能上传多个作品，记录当前正在查看的作品的序号
    const comparingUserShapeIndexMap = reactive({});
    const compareUserProgressIndexMap = reactive({});
    const comparePlayingSpeedTimesMap = reactive({});
    const compareWorkIndex = ref(0);
    const isShowThumbnail = ref(true);
    const userWorks = ref([]);

    const students = computed(() => {
      let list;
      try {
        list = JSON.parse(props.students);
      } catch (e) {
        console.error('parse students error!');
        console.error(e);
        list = [];
      }
      return list;
    });
    const widgetSize = computed(() => {
      const width = utils.getPropOfStringifiedObj(widgetModel.value, 'widgetWidth', 800);
      const height = utils.getPropOfStringifiedObj(widgetModel.value, 'widgetHeight', 600);
      return { width, height };
    });


    watch([results], () => loadUserData());
    watch([isSortByTime], () => loadUserData());
    let xAxisData = computed(() => {
      let temp = []
      userWorks?.value.forEach((item) => {
        item.works.forEach((work) => {
          let equation = work.drawing.generateEquation.Equation
          if (temp.indexOf(equation) === -1) {
            temp.push(work.drawing.generateEquation.Equation)
          }
        })
      })
      return temp;
    });

    const imgs = computed(() => {
      let userSnapshots = userWorks?.value.reduce((all, a) => {
        const { userId, userName, lastUpdatedAt, lastUpdatedAtTimestamp, works } = a;
        if (userNameFilter.value && userName.indexOf(userNameFilter.value) === -1) return all;
        all.push({
          userId, userName, lastUpdatedAt, lastUpdatedAtTimestamp,
          works: works.map((work) => {
            return {
              Equation: work.drawing.generateEquation.Equation,
              operations: work.operations,
              imageDataURL: snapshotWork(work),
              created_at: work.created_at,
              created_at_timestamp: work.created_at_timestamp
            };
          })
        });
        return all;
      }, []);
      let temp = []
      for (let i = 0; i <  xAxisData.value.length; i++) {
        let shouldBreak = false; // 用于标记是否应该跳出外层循环
        const element1 =  xAxisData.value[i];
        for (let k = 0; k < userSnapshots.length; k++) {
          const { works } = userSnapshots[k];
          for (let k = 0; k < works.length; k++) {
            const work = works[k];
            if (work.Equation === element1 && temp.length < xAxisData.value.length) {
               temp.push(work.imageDataURL)
                shouldBreak =true
               if(shouldBreak) break;
            }
          }
          if(shouldBreak) break;
        }
        
      }
      return temp
      // xAxisData.value.forEach((a) => {
      //   let shouldBreak = false; // 用于标记是否应该跳出外层循环
      //   userSnapshots.forEach((item) => {
      //     const { works } = item
      //     works.forEach((work) => {
      //       console.log("AAAAAAAAAAA",a);
      //       if (work.Equation === a && temp.length < xAxisData.value.length) {
      //          temp.push(work.imageDataURL)
      //           shouldBreak =true
      //          if(shouldBreak) return;
             
      //       }
      //     });
      //     if(shouldBreak) return;
      //   });
      // });
   
    })
    let yAxisData = computed(() => {
      let temp = new Array(xAxisData.value.length).fill(0);
      userWorks?.value.forEach((item) => {
        item.works.forEach((work) => {
          let equation = work.drawing.generateEquation.Equation
          let index = xAxisData.value.indexOf(equation)
          if (index != -1) {
            temp[index]++;
          }
        })
      })
      return temp
    });

    function loadGraph() {
      let chartDom = document.getElementById("classifiyStatistics");
      let myChart = echarts.init(chartDom);
      let option;
      console.log("xAxisData", xAxisData.value);
      console.log("yAxisData", yAxisData.value);
      console.log("imgs", imgs.value);
      let imageTags = Array.from({ length: xAxisData.value.length }, (_, index) => `img${index + 1}`);
      option = {
        tooltip: {
          trigger: 'axis',
        },

        grid: {
          containLabel: true
        },
        xAxis: [
          {//显示日期的横坐标
            name: '种类',
            triggerEvent: false,
            type: 'category',
            data: xAxisData.value,
            axisPointer: {
              type: 'shadow'
            },
            axisLabel: {
              interval: 0, // 强制显示所有标签
              // rotate: 45 // 根据需要旋转标签
            },
            min: 0,
          },
          {//显示图片的横坐标
            type: 'category',
            position: 'bottom',
            min: 0,
            offset: 25,
            axisPointer: {
              type: 'none'
            },
            axisTick: {
              show: false
            },
            axisLine: {
              show: false
            },
            //  triggerEvent: true,//添加点击事件
            inverse: true,
            data: imageTags.reverse(),
            axisLabel: {
              formatter: function (value, index) {
                        return `{${value}| }\n{value|' + value + '}`
                            .replace('{value}', '{')
                            .replace(/value\|'/, `' + imgs.value[${index}] + '|`);
                    },
              // formatter: function (value) {
              //   return '{' + value + '| }\n{value|' + value + '}';
              // },
              margin: 20,
              // rich: {
              //   value: {
              //     lineHeight: 50,
              //     align: 'center'
              //   },
              //   img1: {
              //     height: 100,
              //     width: 130,
              //     align: 'center',
              //     backgroundColor: {
              //       image: imgs.value[0]
              //     }
              //   },
              //   img2: {
              //     height: 100,
              //     width: 130,
              //     align: 'center',
              //     backgroundColor: {
              //       image: imgs.value[1]
              //     }
              //   },
              //   img3: {
              //     height: 100,
              //     width: 130,
              //     align: 'center',
              //     backgroundColor: {
              //       image: imgs.value[2]
              //     }
              //   },
              //   img4: {
              //     height: 100,
              //     width: 130,
              //     align: 'center',
              //     backgroundColor: {
              //       image: imgs.value[3]
              //     }
              //   },

              // }
            }
          },
        ],
        yAxis:
        {
          type: 'value',
          name: '人数',
          min: 0
        },


        series: [
          {
            name: '人数',
            type: 'bar',
            barWidth: 40,
            yAxisIndex: 0,
            itemStyle: {
              normal: {
                color: '#13c4db' //改变折线颜色
              }
            },
            data: yAxisData.value,
            label: {
              // 柱状图上的文字设置
              show: true, // 是否显示
              position: 'top',
              textStyle: {
                color: "#000", // 顶部数据的颜色
                fontSize: 18, // 顶部数据的字体大小
              },
              // rotate: 45 // 根据需要旋转标签
            },
          },
        ]
      };
      option && myChart.setOption(option);
    }
    async function loadUserData() {
      const parsedResults = utils.parseStringifiedObj(results.value);
      if (!parsedResults || parsedResults.length === 0) return [];
      const allResults = {};
      students.value.forEach((student) => {
        allResults[student.id] = { userId: student.id, userName: student.name, works: [] };
      });
      if (parsedResults[0][0].content[0].action !== 'upload') {
        parsedResults.forEach((items) => {
          const uResult = { userId: 0, userName: '', works: [] };
          items.forEach((item) => {
            if (!uResult.userId) {
              uResult.userId = item.user.id;
              uResult.userName = item.user.name;
            }
            item.content.forEach((record) => {
              const createDate = new Date(item.created_at);
              if (record.action === 'submitResult' && record.data?.operations?.length) {
                uResult.works.push({
                  operations: record.data.operations,
                  created_at: createDate.toLocaleString(),
                  created_at_timestamp: createDate.getTime()
                });
              }
            })
          });
          if (uResult.works.length !== 0) {
            uResult.lastUpdatedAt = uResult.works[uResult.works.length - 1].created_at;
            uResult.lastUpdatedAtTimestamp = uResult.works[uResult.works.length - 1].created_at_timestamp;
          }
          uResult.works = orderBy(uResult.works, 'created_at_timestamp', ['desc']);
          if (uResult.userId) {
            allResults[uResult.userId] = uResult
          }
        });
      } else {
        for (let i = 0; i < parsedResults.length; i++) {
          const uResult = { userId: 0, userName: '', works: [] };
          let items = parsedResults[i];
          for (let j = 0; j < items.length; j++) {
            let item = items[j];
            if (!uResult.userId) {
              uResult.userId = item.user.id;
              uResult.userName = item.user.name;
            }
            for (let m = 0; m < item.content.length; m++) {
              let record = item.content[m];
              const createDate = new Date(item.created_at);
              if (record.action === "upload" && record.data?.file?.url) {
                const url = record.data.file.url;
                await new Promise((resolve) => {
                  window.$.getJSON(url, (data) => {

                    uResult.works.push({
                      answer: data.answer,
                      operations: data.allOperations,
                      drawing: data.drawing,
                      created_at: createDate.toLocaleString(),
                      created_at_timestamp: createDate.getTime()
                    });
                    resolve('resolved');
                  });
                });
              }
            }
          }
          if (uResult.works.length !== 0) {
            uResult.lastUpdatedAt = uResult.works[uResult.works.length - 1].created_at;
            uResult.lastUpdatedAtTimestamp = uResult.works[uResult.works.length - 1].created_at_timestamp;
          }
          uResult.works = orderBy(uResult.works, 'created_at_timestamp', ['desc']);
          if (uResult.userId) {
            allResults[uResult.userId] = uResult
          }
        }
      }
      console.log('userResults: ', allResults);
      let sortedData = students.value.map(student => allResults[student.id]);
      if (isSortByTime.value) {
        sortedData = orderBy(sortedData, [(d) => d.lastUpdatedAtTimestamp || Infinity], ["asc"]);
      }
      userWorks.value = sortedData;
      loadGraph()
      // console.log(sortedData);
    }


    function prefixBookDir(relPath) {
      return bookDir.value.replace(/\/+$/, '') + '/' + relPath.replace(/^\/+/, '');
    }

    // 图片的初始状态
    const initialImages = computed(() => {
      return utils.getPropOfStringifiedObj(widgetModel.value, 'initialImages', []).map((img) => ({
        ...img,
        image: prefixBookDir(img.image)
      }));
    });
    const materials = computed(() => {
      return utils.getPropOfStringifiedObj(widgetModel.value, 'materials', []).reduce((ms, m) => {
        if (m.image) {
          ms.push({ ...m, image: prefixBookDir(m.image) });
        }
        return ms;
      }, []);
    });
    const materialImageSrcs = computed(() => {
      return materials.value.reduce((srcs, m) => {
        if (srcs.indexOf(m.image) === -1) srcs.push(m.image);
        return srcs;
      }, []);
    });
    let loadedMaterialImageSrcs = [];
    const materialImages = reactive([]);

    function loadMaterialImages() {
      loadedMaterialImageSrcs = [];
      materials.value.forEach((m) => {
        const imgSrc = m.image;
        const exist = find(materialImages, { src: imgSrc });
        if (!exist) {
          const imgElem = new Image();
          imgElem.crossOrigin = 'use-credentials';
          imgElem.onload = () => {
            if (!find(materialImages, { src: imgSrc })) materialImages.push({ src: imgSrc, el: imgElem })
            if (loadedMaterialImageSrcs.indexOf(imgSrc) === -1) loadedMaterialImageSrcs.push(imgSrc);
          };
          imgElem.src = imgSrc;
        }
      });
    }

    watch(materials, loadMaterialImages);
    onMounted(() => {
      loadMaterialImages();
      loadUserData();
    });

    /**
     * 该方法从包括undo/redo记录的全操作记录生成一个不包括undo/redo记录的最终的operations记录
     * @param {Array} allOperations 包括undo/redo操作的全记录
     */
    function getOpeartionsFromAllOperations(allOperations) {
      const operations = [];
      let currentOpeartionIndex = -1;
      for (let i = 0; i < allOperations.length; i++) {
        const type = allOperations[i]?.type;
        if (type === 'undo') {
          if (currentOpeartionIndex !== -1) {
            currentOpeartionIndex -= 1;
          }
        } else if (type === 'redo') {
          if (currentOpeartionIndex < operations.length - 1) {
            currentOpeartionIndex += 1;
          }
        } else {
          if (currentOpeartionIndex !== operations.length - 1) {
            while (operations.length > currentOpeartionIndex + 1) operations.pop();
          }
          operations.push(allOperations[i]);
          currentOpeartionIndex = operations.length - 1;
        }
      }
      if (currentOpeartionIndex !== operations.length - 1) {
        while (operations.length > currentOpeartionIndex + 1) operations.pop();
      }
      return operations;
    }

    function getImagesFromOperation(operation) {
      if (operation?.type !== 'moveImage' || !isArray(operation?.data)) return [];
      // operation.data 存储每张图片的路径、大小、位置
      return operation.data.reduce((images, img) => {
        const { mi, t, l, w, h, r, ox, oy, iv } = img;
        const material = materials.value[mi];
        if (material) {
          images.push({
            image: material.image,
            top: t,
            left: l,
            width: w,
            height: h,
            rotation: r || 0,
            originX: typeof ox === 'number' ? ox : 50,
            originY: typeof oy === 'number' ? oy : 50,
            hidden: !!iv,
          });
        }
        return images;
      }, []);
    }

    function getImagesOfWork(work) {
      const operations = getOpeartionsFromAllOperations(work.operations);
      for (let i = operations.length - 1; i >= 0; i--) {
        const { type } = operations[i];
        if (type === 'moveImage') return getImagesFromOperation(operations[i]);
      }
      return initialImages.value;
    }

    function getStrokesOfWork(work) {
      const operations = getOpeartionsFromAllOperations(work.operations);
      const strokes = operations.reduce((allStrokes, op) => {
        if (op.type === 'stroke') allStrokes.push(op.data);
        return allStrokes;
      }, []);
      return strokes;
    }

    function getRecordStrokesOfWork(work) {
      const operations = getOpeartionsFromAllOperations(work.operations);
      const strokes = operations.reduce((allStrokes, op) => {
        if (op.type === 'moveImage' && op.recordStrokeData) allStrokes.push(op.recordStrokeData);
        return allStrokes;
      }, []);
      return strokes;
    }

    let snapshotCanvas = document.createElement('canvas');
    let snapshotCtx = snapshotCanvas.getContext('2d');

    function snapshotWork(work) {
      snapshotCanvas.width = widgetSize.value.width;
      snapshotCanvas.height = widgetSize.value.height;
      const images = getImagesOfWork(work);
      images.forEach((img) => {
        if (img.hidden) return;
        const loadedImg = find(materialImages, { src: img.image });
        if (loadedImg) {
          if (img.rotation) {
            const originX = typeof img.originX === 'number' ? img.originX : 50;
            const originY = typeof img.originY === 'number' ? img.originY : 50;
            const originOffset = [originX * img.width / 100, originY * img.height / 100];
            const center = [img.left + originOffset[0], img.top + originOffset[1]];
            const rad = img.rotation * Math.PI / 180;
            snapshotCtx.translate(center[0], center[1]);
            snapshotCtx.rotate(rad);
            snapshotCtx.drawImage(loadedImg.el, -originOffset[0], -originOffset[1], img.width, img.height);
            snapshotCtx.rotate(-rad);
            snapshotCtx.translate(-center[0], -center[1]);
          } else {
            snapshotCtx.drawImage(loadedImg.el, img.left, img.top, img.width, img.height);
          }
        }
      });

      const strokes = getStrokesOfWork(work);
      drawingRender(snapshotCtx, work.drawing)
      strokes.forEach((stroke) => {
        if (stroke.plots.length < 2) return;
        snapshotCtx.strokeStyle = stroke.color;
        snapshotCtx.lineWidth = stroke.width;
        snapshotCtx.lineJoin = 'round';
        snapshotCtx.setLineDash(stroke.lineDash || []);
        snapshotCtx.beginPath();
        snapshotCtx.moveTo(stroke.plots[0][0], stroke.plots[0][1]);
        for (let i = 1; i < stroke.plots.length; i++) {
          snapshotCtx.lineTo(stroke.plots[i][0], stroke.plots[i][1]);
        }
        snapshotCtx.stroke();
      });

      const recordStrokes = getRecordStrokesOfWork(work);
      if (recordStrokes.length !== 0) {
        let recordStroke = recordStrokes[recordStrokes.length - 1]
        snapshotCtx.beginPath();
        snapshotCtx.arc(recordStroke.l, recordStroke.t, 15, 0, 2 * Math.PI);
        snapshotCtx.fillStyle = 'rgb(255,255,153)';
        snapshotCtx.fill();
      }
      return snapshotCanvas.toDataURL('image/png');
    }

    const userNameFilter = ref('');
    const userSnapshots = ref([]);

    function calcUserSnapshots() {
      userSnapshots.value = userWorks.value.reduce((all, a) => {
        const { userId, userName, lastUpdatedAt, lastUpdatedAtTimestamp, works } = a;
        if (userNameFilter.value && userName.indexOf(userNameFilter.value) === -1) return all;
        all.push({
          userId, userName, lastUpdatedAt, lastUpdatedAtTimestamp,
          works: works.map((work) => {
            return {
              Equation: work.drawing.generateEquation.Equation,
              operations: work.operations,
              imageDataURL: snapshotWork(work),
              created_at: work.created_at,
              created_at_timestamp: work.created_at_timestamp
            };
          })
        });
        return all;
      }, []);
    }

    watch(materialImages, () => {
      // 图片全部加载完以后，重新计算一下所有学生的缩略图
      if (loadedMaterialImageSrcs.length === materialImageSrcs.value.length) calcUserSnapshots();
    });
    watch(userWorks, calcUserSnapshots);
    const debouncedCalcUserSnapshots = debounce(calcUserSnapshots, 500);
    watch(userNameFilter, debouncedCalcUserSnapshots);
    const pUserSnapshots = computed(() => {
      return userSnapshots.value.reduce((all, a) => {
        if (a.works.length !== 0) all.push(a);
        return all;
      }, []);
    });



    watch(pUserSnapshots, () => {
      console.log(pUserSnapshots.value);
    });
    const nUserSnapshots = computed(() => {
      return userSnapshots.value.reduce((all, a) => {
        if (a.works.length === 0) all.push(a);
        return all;
      }, []);
    });

    const collection = reactive([]); // 收藏夹
    const collectionIndex = ref(0);
    const isCollectionModalVisible = ref(false);

    function onOpenCollection() {
      if (collectionIndex.value >= collection.length) {
        collectionIndex.value = 0;
      }
      isCollectionModalVisible.value = true;
    }

    function isInCollection(userInfo, work) {
      return findIndex(collection, { userId: userInfo.userId, created_at_timestamp: work.created_at_timestamp }) !== -1;
    }

    function switchCollection(userInfo, work) {
      const index = findIndex(collection, { userId: userInfo.userId, created_at_timestamp: work.created_at_timestamp });
      if (index !== -1) {
        collection.splice(index, 1);
      } else {
        collection.push({
          userId: userInfo.userId,
          userName: userInfo.userName,
          operations: work.operations,
          url: work.imageDataURL,
          created_at: work.created_at,
          created_at_timestamp: work.created_at_timestamp
        });
      }
      if (collectionIndex.value >= collection.length) {
        collectionIndex.value = collection.length - 1;
      }
      if (collectionIndex.value === -1 && collection.length) {
        collectionIndex.value = 0;
      }
      if (isCollectionModalVisible.value) {
        stopPlayWork(userInfo.userId);
        playOperationIndex = -1;
        playingProgress.value = 0;
        isPaused.value = false;
        if (collectionPlayingCanvasRef.value) {
          // eslint-disable-next-line no-self-assign
          collectionPlayingCanvasRef.value.width = collectionPlayingCanvasRef.value.width;
        }
      }
    }

    function updateCollectionIndex(index) {
      if (collectionIndex.value === index) return;
      collectionIndex.value = index;
      stopPlayWork();
      playOperationIndex = -1;
      playingProgress.value = 0;
      isPaused.value = false;
      if (collectionPlayingCanvasRef.value) {
        // eslint-disable-next-line no-self-assign
        collectionPlayingCanvasRef.value.width = collectionPlayingCanvasRef.value.width;
      }
    }

    const singleUserId = ref(0);
    const compareSingleUserId = ref(0);
    const isSingleUserModalVisible = ref(false);
    const singleUserWorks = computed(() => {
      for (const work of userWorks.value) {
        if (work.userId === singleUserId.value) return work;
      }
      return null;
    });
    const compareSingleUserWorks = computed(() => {
      for (const work of userWorks.value) {
        if (work.userId === compareSingleUserId.value) {
          return work;
        }
      }
      return null;
    });
    const singleUserSnapshots = computed(() => {
      for (const u of userSnapshots.value) {
        if (u.userId === singleUserId.value) {
          return u;
        }
      }
      return null;
    });
    const compareSingleUserSnapshots = computed(() => {
      for (const u of userSnapshots.value) {
        if (u.userId === compareSingleUserId.value) {
          return u;
        }
      }
      return null;
    });

    function previewUserWorks(userId) {
      singleUserId.value = userId;
      userWorkIndexMap[userId] = 0;
      isSingleUserModalVisible.value = true;
      compareProgressArr.value = [];
    }

    const isAllUserNamesVisible = ref(false);
    const userFullNameVisibility = reactive({});

    function toggleFullName(userId) {
      userFullNameVisibility[userId] = !userFullNameVisibility[userId];
    }

    function getUserWorkIndex(userInfo) {
      if (userWorkIndexMap[userInfo.userId] !== undefined) {
        return userWorkIndexMap[userInfo.userId];
      }
      return 0;
    }

    function getCompareUserWorkIndex(userInfo) {
      if (comparingUserShapeIndexMap[userInfo.userId] !== undefined) {
        return comparingUserShapeIndexMap[userInfo.userId];
      }
      return 0;
    }


    function setComparingUserShapeIndex(userId, index) {
      if (comparingUserShapeIndexMap[userId] === index) return;
      compareProgressArr.value.forEach((item) => {
        if (userId === item.userId) {
          item.progressIndex = 0;
          item.isPlaying = false;
          compareIsClick[userId + '-' + item.index] = false;
          compareUserProgressIndexMap[userId + '-' + item.index] = 0;
          if (item.playTimer) {
            clearTimeout(item.playTimer);
            item.playTimer = null;
          }
          let canvasRefObj = compareProgressArr.value.filter(item => {
            return item.userId === userId && index === item.index
          });
          if (canvasRefObj.length > 0) {
            // eslint-disable-next-line no-self-assign
            playingCompareCanvasRef.value[canvasRefObj[0].compareCanvasRefIndex].width = playingCompareCanvasRef.value[canvasRefObj[0].compareCanvasRefIndex].width;
          }
        }
      });
      comparingUserShapeIndexMap[userId] = index;
    }

    function updateUserWorkIndex(userId, index) {
      if (userWorkIndexMap[userId] === index) return;
      userWorkIndexMap[userId] = index;
      stopPlayWork(userId);
      playOperationIndex = -1;
      playingProgress.value = 0;
      isPlaying.value = false;
      isPaused.value = false;
      if (playingCanvasRef.value) {
        // eslint-disable-next-line no-self-assign
        playingCanvasRef.value.width = playingCanvasRef.value.width;
      }
    }

    const isPlaying = ref(false);
    const isPaused = ref(false);
    const playingCanvasRef = ref(null);
    const playingCompareCanvasRef = ref(null);
    const collectionPlayingCanvasRef = ref(null);
    const playingProgress = ref(0);
    const playingSpeedTimes = ref(1);
    const playingSpeedTimesName = computed(() => `${playingSpeedTimes.value}倍速`);
    let operationInterval = 1000;
    let playTimer = null;
    let playOperationIndex = -1;
    let compareIsClick = reactive({});

    function getProgressIndex(userInfo) {
      let index = 0;
      if (comparingUserShapeIndexMap[userInfo.userId]) index = comparingUserShapeIndexMap[userInfo.userId];
      let progressObj = compareProgressArr.value.filter(item => {
        return userInfo.userId === item.userId && index === item.index;
      });
      if (progressObj.length > 0) {
        return progressObj[0].progressIndex === 0;
      }
    }

    function getCompareWorkIndex(userId) {
      let index = 0;
      if (comparingUserShapeIndexMap[userId]) {
        index = comparingUserShapeIndexMap[userId]
      }
      return index;
    }

    function setCompareIsClick(userId, i, playing) {
      if (comparingUserShapeIndexMap[userId]) {
        compareWorkIndex.value = comparingUserShapeIndexMap[userId];
      } else {
        compareWorkIndex.value = 0;
      }
      let isPlaying = false;
      if (!playing) {
        isPlaying = true;
      }
      compareIsClick[userId + '-' + compareWorkIndex.value] = isPlaying;
      compareSingleUserId.value = userId;
      compareCanvasRefIndex.value = i;
      playPauseWork();
    }

    function playPauseWork() {
      if (!isComparingModalVisible.value) {
        if (isPlaying.value) {
          stopPlayWork();
          isPaused.value = true;
        } else {
          playWork();
          isPaused.value = false;
        }
      } else {
        let currentPlaying = compareIsClick[compareSingleUserId.value + '-' + compareWorkIndex.value];
        if (currentPlaying) {
          let isHasPlayTimer = compareProgressArr.value.filter(item => {
            return item.playTimer !== null;
          });
          if (isHasPlayTimer.length === 0) {
            playCurrentWork();
          } else {
            playHistoryWork(isHasPlayTimer);
            playCurrentWork();
          }
        } else {
          stopPlayWork();
        }
      }
    }

    function playCurrentWork() {
      const work = getCurrentWork();
      if (!work) {
        message.error('未找到对应的学生作品!');
        return;
      }
      let compareOperationInterval = 1000;
      let compareOperationIntervalArr = compareProgressArr.value.filter(item => {
        return item.userId === compareSingleUserId.value && item.index === compareWorkIndex.value
      })
      if (compareOperationIntervalArr.length > 0) {
        compareOperationInterval = 1000 / compareOperationIntervalArr[0].times;
      }
      if (compareOperationIntervalArr[0].playTimer) clearTimeout(compareOperationIntervalArr[0].playTimer);
      compareOperationIntervalArr[0].playTimer = setTimeout(() => playWorkLoop(work, 'compare', compareOperationIntervalArr[0]), compareOperationInterval);
    }

    function playHistoryWork(isHasPlayTimer) {
      isHasPlayTimer.forEach(item => {
        const work = item.userInfo.works[item.index];
        const nextOperation = item.userInfo.works[item.index].operations[item.progressIndex + 1];
        if (!work) {
          message.error('未找到对应的学生作品!');
          return;
        }
        if (item.progressIndex > work.operations.length - 1) {
          item.progressIndex = 0;
          clearTimeout(item.playTimer);
          item.playTimer = null;
          return;
        }
        let compareOperationInterval = 1000 / item.times;
        item.playTimer = setTimeout(() => playWorkLoop(work, 'compare', item, nextOperation), compareOperationInterval);
      });
    }

    function setPlayingSpeed(times) {
      playingSpeedTimes.value = times;
      operationInterval = 1000 / times;
    }

    function setComparePlayingSpeed(times, userId, index) {
      comparePlayingSpeedTimesMap[userId + ' ' + index] = times;
      let speedArr = compareProgressArr.value.filter(item => {
        return userId === item.userId && index === item.index
      });
      if (speedArr.length > 0) {
        speedArr[0].times = times;
      }
    }

    function getComparePlayingSpeed(userId, index) {
      let times = 1;
      if (index !== undefined) {
        let speedArr = compareProgressArr.value.filter(item => {
          return userId === item.userId && index === item.index
        });
        if (speedArr.length > 0) {
          times = speedArr[0].times
        }
        return times;
      } else {
        return 1;
      }
    }

    function onChangePlayingProgress(value) {
      const work = getCurrentWork();
      if (!work) {
        message.error('未找到对应的学生作品!');
        return;
      }
      playOperationIndex = value - 1;
      isPlaying.value = false;
      isPaused.value = true;
      setTimeout(() => renderPlayingCanvas(work), 30);
    }

    function onChangeComparePlayingProgress(params) {
      let value = parseFloat(params.split('-')[0]);
      compareSingleUserId.value = parseFloat(params.split('-')[1]);
      const work = getCurrentWork();
      if (!work) {
        message.error('未找到对应的学生作品!');
        return;
      }
      let currentWorkArr = compareProgressArr.value.filter(item => {
        return item.userId === compareSingleUserId.value && item.index === compareWorkIndex.value
      });
      if (currentWorkArr.length > 0) {
        currentWorkArr[0].progressIndex = value;
        compareUserProgressIndexMap[currentWorkArr[0].userId + '-' + compareWorkIndex.value] = currentWorkArr[0].progressIndex;
        compareIsClick[compareSingleUserId.value + '-' + compareWorkIndex.value] = false;
      }
      setTimeout(() => renderPlayingCanvas(work, '', currentWorkArr[0].compareCanvasRefIndex), 30);
    }

    function getCurrentWork() {
      if (isSingleUserModalVisible.value) {
        const workIndex = userWorkIndexMap[singleUserId.value];
        return singleUserWorks.value.works[workIndex];
      } else if (isCollectionModalVisible.value) {
        return collection[collectionIndex.value];
      } else if (isComparingModalVisible.value) {
        return compareSingleUserWorks.value.works[compareWorkIndex.value];
      }
      return null;
    }

    function playWork() {
      const work = getCurrentWork();
      if (!work) {
        message.error('未找到对应的学生作品!');
        return;
      }
      if (playOperationIndex >= work.operations.length - 1) {
        playOperationIndex = -1;
      }
      if (playOperationIndex === -1) {
        playingProgress.value = 0;
        setTimeout(() => renderPlayingCanvas(work), 30);
      }
      isPlaying.value = true;
      if (playTimer) clearTimeout(playTimer);
      playTimer = setTimeout(() => playWorkLoop(work), operationInterval);
    }

    function stopPlayWork() {
      if (!isComparingModalVisible.value) {
        if (playTimer) {
          clearTimeout(playTimer);
          playTimer = null;
        }
        isPlaying.value = false;
      } else {
        if (compareSingleUserId.value) {
          let compareOperationIntervalArr = compareProgressArr.value.filter(item => {
            return item.userId === compareSingleUserId.value && item.index === compareWorkIndex.value
          });
          if (compareOperationIntervalArr.length > 0) {
            if (compareOperationIntervalArr[0].playTimer) clearTimeout(compareOperationIntervalArr[0].playTimer);
            compareOperationIntervalArr[0].playTimer = null;
          }
        }
      }
    }

    // 获取指定操作序号前面的笔迹
    function getStrokesBeforeIndex(work, index) {
      const operations = work.operations.slice(0, index + 1);
      return getStrokesOfWork({ operations });
    }

    // 获取指定操作序号前面的图片状态
    function getImagesBeforeIndex(work, index) {
      const operations = work.operations.slice(0, index + 1);
      return getImagesOfWork({ operations });
    }

    function playWorkLoop(work, params, item, nextOperation) {
      let operation;
      if (!params) {
        operation = getNextOperation(work);
      } else {
        operation = nextOperation;
        if (!nextOperation) {
          operation = getNextOperation(work);
        }
      }
      let canvas = playingCanvasRef;
      if (isCollectionModalVisible.value) {
        canvas = collectionPlayingCanvasRef;
      }
      if (isComparingModalVisible.value) {
        canvas = playingCompareCanvasRef;
      }
      if (operation) {
        if (!isComparingModalVisible.value) {
          renderPlayingCanvas(work);
          playTimer = setTimeout(() => playWorkLoop(work), operationInterval);
        } else {
          compareUserProgressIndexMap[item.userId + '-' + item.index] = item.progressIndex;
          renderPlayingCanvas(work, item);
          item.progressIndex++;
          let compareOperationInterval = 1000;
          let compareOperationIntervalArr = compareProgressArr.value.filter(item => {
            return item.userId === compareSingleUserId.value && item.index === compareWorkIndex.value
          });
          if (compareOperationIntervalArr.length > 0) {
            compareOperationInterval = 1000 / compareOperationIntervalArr[0].times;
          }
          if (item.progressIndex > work.operations.length - 1) {
            item.progressIndex = 0;
            if (item.playTimer) clearTimeout(item.playTimer);
            item.playTimer = null;
            compareIsClick[item.userId + '-' + item.index] = false;
            return;
          }
          const nextOperation = item.userInfo.works[item.index].operations[item.progressIndex];
          if (item.playTimer) clearTimeout(item.playTimer);
          item.playTimer = setTimeout(() => playWorkLoop(work, 'compare', item, nextOperation), compareOperationInterval);
        }
      } else {
        if (isComparingModalVisible.value) {
          if (item.progressIndex >= work.operations.length - 1) {
            if (item.playTimer) clearTimeout(item.playTimer);
            item.isPlaying = false;
            compareIsClick[item.userId + '-' + item.index] = false;
            compareUserProgressIndexMap[item.userId + '-' + item.index] = item.progressIndex;
          }
        } else {
          isPlaying.value = false;
          playOperationIndex = -1;
          playingProgress.value = work.operations.length;
          // eslint-disable-next-line no-self-assign
          canvas.value.width = canvas.value.width;
        }
      }
    }

    function renderPlayingCanvas(work, item, onchangeRefIndex) {
      let canvas = playingCanvasRef;
      if (isCollectionModalVisible.value) {
        canvas = collectionPlayingCanvasRef;
      }
      if (isComparingModalVisible.value) {
        canvas = playingCompareCanvasRef;
      }
      let canvasRect, ctx;
      let currentIndex = playOperationIndex;
      if (isComparingModalVisible.value) {
        let canvasRefIndex = compareCanvasRefIndex.value;
        let currentWorkArr = compareProgressArr.value.filter(item => {
          return item.userId === compareSingleUserId.value && item.index === compareWorkIndex.value
        });
        currentIndex = currentWorkArr[0].progressIndex;
        if (item) {
          currentIndex = item.progressIndex;
          canvasRefIndex = item.compareCanvasRefIndex;
        }
        if (onchangeRefIndex !== undefined) {
          canvasRefIndex = onchangeRefIndex;
        }
        canvasRect = canvas.value[canvasRefIndex].getBoundingClientRect();
        canvas.value[canvasRefIndex].width = canvasRect.width;
        canvas.value[canvasRefIndex].height = canvasRect.height;
        ctx = canvas.value[canvasRefIndex].getContext('2d');
      } else {
        canvasRect = canvas.value.getBoundingClientRect();
        canvas.value.width = canvasRect.width;
        canvas.value.height = canvasRect.height;
        ctx = canvas.value.getContext('2d');
      }
      const scaleRatio = canvasRect.width / widgetSize.value.width;

      const images = getImagesBeforeIndex(work, currentIndex);
      images.forEach((img) => {
        if (img.hidden) return;
        const loadedImg = find(materialImages, { src: img.image });
        if (loadedImg) {
          const x = img.left * scaleRatio;
          const y = img.top * scaleRatio;
          const w = img.width * scaleRatio;
          const h = img.height * scaleRatio;
          if (img.rotation) {
            const originX = typeof img.originX === 'number' ? img.originX : 50;
            const originY = typeof img.originY === 'number' ? img.originY : 50;
            const originOffset = [originX * w / 100, originY * h / 100];
            const center = [x + originOffset[0], y + originOffset[1]];
            const angle = img.rotation * Math.PI / 180;
            ctx.translate(center[0], center[1]);
            ctx.rotate(angle);
            ctx.drawImage(loadedImg.el, -originOffset[0], -originOffset[1], w, h);
            ctx.rotate(-angle);
            ctx.translate(-center[0], -center[1]);
          } else {
            ctx.drawImage(loadedImg.el, x, y, w, h);
          }
        }
      });

      const strokes = getStrokesBeforeIndex(work, currentIndex);
      strokes.forEach((stroke) => {
        const scaledLineWidth = stroke.width * scaleRatio;
        ctx.strokeStyle = stroke.color;
        ctx.lineWidth = scaledLineWidth > 1 ? scaledLineWidth : 1;
        ctx.lineJoin = 'round';
        ctx.setLineDash(stroke.lineDash ? stroke.lineDash.map(n => n * scaleRatio) : []);
        ctx.beginPath();
        ctx.moveTo(stroke.plots[0][0] * scaleRatio, stroke.plots[0][1] * scaleRatio);

        for (let k = 1; k < stroke.plots.length; k++) {
          ctx.lineTo(stroke.plots[k][0] * scaleRatio, stroke.plots[k][1] * scaleRatio);
        }
        ctx.stroke();
      });

      if (currentIndex === -1 || currentIndex >= work.operations.length) return;
      const recordStroke = work.operations[currentIndex].recordStrokeData;
      if (recordStroke) {
        ctx.beginPath();
        ctx.arc(recordStroke.l * scaleRatio, recordStroke.t * scaleRatio, 15 * scaleRatio, 0, 2 * Math.PI);
        ctx.fillStyle = 'rgb(255,255,153)';
        ctx.fill();
      }
    }

    function getNextOperation(work) {
      if (!isComparingModalVisible.value) {
        const nextOperationIndex = ++playOperationIndex;
        if (nextOperationIndex > (work.operations.length - 1)) {
          playingProgress.value = work.operations.length;
        } else {
          playingProgress.value = nextOperationIndex + 1;
        }
        return work.operations[nextOperationIndex];
      } else {
        let currentWorkArr = compareProgressArr.value.filter(item => {
          return item.userId === compareSingleUserId.value && item.index === compareWorkIndex.value
        })
        let nextProgressIndex = currentWorkArr[0].progressIndex + 1;
        compareUserProgressIndexMap[currentWorkArr[0].userId + '-' + compareWorkIndex.value] = currentWorkArr[0].progressIndex;
        return work.operations[nextProgressIndex];
      }
    }


    watch(isSingleUserModalVisible, () => {
      stopPlayWork();
      playOperationIndex = -1;
      playingProgress.value = 0;
      isPaused.value = false;
      if (playingCanvasRef.value) {
        // eslint-disable-next-line no-self-assign
        playingCanvasRef.value.width = playingCanvasRef.value.width;
      }
    });
    watch(isComparingModalVisible, () => {
      stopPlayWork();
      compareProgressArr.value = [];
      compareWorkIndex.value = 0;
      compareSingleUserId.value = 0;
      if (playingCompareCanvasRef.value) {
        // eslint-disable-next-line no-self-assign
        playingCompareCanvasRef.value[compareCanvasRefIndex.value].width = playingCompareCanvasRef.value[compareCanvasRefIndex.value].width;
      }
      if (isComparingModalVisible.value && comparingUserOriginalShapes.value.length > 1) {
        comparingUserOriginalShapes.value.forEach((item, ind) => {
          item.works.forEach((ite, index) => {
            compareProgressArr.value.push({
              userInfo: item,
              userId: item.userId,
              index,
              progressIndex: 0,
              isPlaying: false,
              playTimer: null,
              times: 1,
              compareCanvasRefIndex: ind
            });
          });
        });
      }
    });

    watch(isCollectionModalVisible, () => {
      stopPlayWork();
      playOperationIndex = -1;
      playingProgress.value = 0;
      isPaused.value = false;
      if (collectionPlayingCanvasRef.value) {
        // eslint-disable-next-line no-self-assign
        collectionPlayingCanvasRef.value.width = collectionPlayingCanvasRef.value.width;
      }
    });

    function onCompareUsers() {
      isComparingModalVisible.value = true;
      comparingUserShapeIndexMap[singleUserId] = 0;
    }

    function onCheckBox(userInfo) {
      let currentUserId = userInfo.userId;
      let idArr = comparingUserOriginalShapes.value.filter((item) => {
        return item.userId === currentUserId
      });
      let newIdArr = pUserSnapshots.value.filter((item) => {
        return item.userId === currentUserId
      });
      if (idArr.length === 0) {
        comparingUserOriginalShapes.value.push(newIdArr[0]);
      } else {
        comparingUserOriginalShapes.value = comparingUserOriginalShapes.value.filter((item) => {
          return item.userId !== currentUserId
        });
      }
    }

    function formatName(name) {
      if (!isAllUserNamesVisible.value) {
        let formatName = name.split("")[0];
        for (let i = 0; i < name.length - 1; i++) {
          formatName += "*";
        }
        return formatName;
      } else {
        return name;
      }
    }

    return {
      yAxisData,
      xAxisData,
      pUserSnapshots,
      nUserSnapshots,
      userNameFilter,
      isSortByTime,
      singleUserId,
      singleUserSnapshots,
      singleUserWorks,
      userFullNameVisibility,
      isAllUserNamesVisible,
      isSingleUserModalVisible,
      isPlaying,
      isPaused,
      playingCanvasRef,
      playingCompareCanvasRef,
      playingProgress,
      playingSpeedTimesName,
      userWorkIndexMap,
      collection,
      collectionIndex,
      isCollectionModalVisible,
      collectionPlayingCanvasRef,
      isComparingModalVisible,
      comparingUserOriginalShapes,
      compareCanvasRefIndex,
      comparingUserShapeIndexMap,
      compareUserProgressIndexMap,
      compareSingleUserWorks,
      compareSingleUserId,
      compareSingleUserSnapshots,
      compareWorkIndex,
      compareProgressArr,
      isShowThumbnail,
      compareIsClick,
      previewUserWorks,
      toggleFullName,
      getUserWorkIndex,
      getCompareUserWorkIndex,
      updateUserWorkIndex,
      playPauseWork,
      onOpenCollection,
      isInCollection,
      switchCollection,
      updateCollectionIndex,
      onChangePlayingProgress,
      onChangeComparePlayingProgress,
      setPlayingSpeed,
      onCompareUsers,
      onCheckBox,
      setComparingUserShapeIndex,
      formatName,
      setComparePlayingSpeed,
      getComparePlayingSpeed,
      setCompareIsClick,
      getProgressIndex,
      getCompareWorkIndex
    };
  }
};
</script>

<style lang="less" scoped>
@import "~ant-design-vue/lib/style/themes/default.less";

#classifiyStatistics {
  width: 1000px;
  height: 600px;
  vertical-align: middle;
  padding: 20px 20px;
  margin: 0 auto;
}

.statistics-container {
  position: absolute;
  width: 100%;
  height: 100vh;
  overflow: auto;
  display: flex;
  min-width: 500px;
  flex-direction: column;
}

.header-tools {
  position: sticky;
  top: 0;
  left: 0;
  width: 100%;
  height: 50px;
  line-height: 50px;
  padding: 0 4px;
  text-align: right;
}

.user-list {
  padding: 1em;
  flex: 1;
  // overflow: auto;

  .user {
    display: inline-block;
    width: calc(25% - 20px);
    margin: 10px;

    .user-info {
      display: flex;

      .user-name {
        font-weight: bold;
        margin-left: 0.5em;

        .hidden-username {
          cursor: pointer;
        }
      }

      margin: 1em 0;

      .update-time {
        white-space: nowrap;
        text-overflow: ellipsis;
        overflow: hidden;
      }
    }

    .thumbnails-container {
      position: relative;
      width: 100%;
      padding-top: 56.25%;
      box-shadow: 0 0 3px 1px #ddd;

      .thumbnail {
        box-sizing: content-box;
        width: 100%;
        height: 100%;
        position: absolute;
        top: 0;
        left: 0;
        cursor: pointer;
        text-align: center;

        img {
          max-width: 100%;
          max-height: 100%;
          background-repeat: no-repeat;
          background-size: 100% 100%;
          background-position: top left;
        }
      }

      .tips {
        text-align: center;
        position: absolute;
        top: 10px;
        width: 100%;
      }

      .count {
        position: absolute;
        width: 20px;
        height: 20px;
        border-radius: 2px;
        background-color: rgba(0, 0, 0, .5);
        bottom: 0;
        right: 0;
        color: #fff;
        text-align: center;
        line-height: 20px;
        font-size: 14px;
        font-weight: bold;
      }

      .thumbnail-tools {
        position: absolute;
        top: 0;
        right: 0;
        width: 40px;
        height: 40px;
        text-align: center;
        line-height: 40px;
      }
    }
  }
}


.n-user-list {
  padding: 0 1em;
  max-height: calc(100% - 300px);
  overflow: auto;

  .n-user-name {
    display: inline-block;
    padding: 2px 8px;
    border: 1px solid #ddd;
    border-radius: 2px;
    margin-right: 0.5em;
    margin-bottom: 0.5em;

    &:last-child {
      margin-right: 0;
    }
  }
}

.comparing-list {
  .comparing-item {
    display: inline-block;
    width: calc(50% - 1px);
    padding: 20px;
    text-align: center;

    .user-name {
      text-align: center;
      font-weight: 500;
      font-size: 16px;
      line-height: 1.5;
      margin: 0 0 1em 0;
    }
  }
}

.preview-image-container {
  position: relative;

  .preview-image-wrapper {
    position: relative;
    background-repeat: no-repeat;
    background-size: 100% 100%;
    background-position: top left;

    canvas {
      position: absolute;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
      z-index: 9;
    }
  }
}

.preview-thumbnails {
  height: 140px;
  padding: 20px 0 0 0;
  white-space: nowrap;
  overflow: auto;
  text-align: center;

  img {
    max-height: 100px;
    margin-right: 10px;
    cursor: pointer;
    background-repeat: no-repeat;
    background-size: 100% 100%;
    background-position: top left;

    &.selected {
      border: 2px solid @primary-color;
    }

    &:last-child {
      margin-right: 0;
    }
  }
}

.collection-playing-canvas {
  display: none;
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  background-color: white;
}

.compare-modal-title {
  color: rgba(0, 0, 0, .85);
  font-weight: 500;
  font-size: 16px;
  line-height: 22px;
  text-align: center;
}
</style>./drawing.js
